import type { Md5ProgressHandler } from './types';

import CryptoJS from 'crypto-js';
// MD5计算工具类，使用crypto-js

export const MD5Utils = {
  /**
   * 计算文件的MD5哈希值 - 使用流式处理避免内存不足
   */
  async calcFile(
    file: File,
    chunkSize: number = 8 * 1024 * 1024,
    onProgress?: Md5ProgressHandler,
  ): Promise<string> {
    // 对于小文件（<10MB），直接使用原来的方法
    if (file.size <= 10 * 1024 * 1024) {
      onProgress?.(0, file.size, 0);
      const buffer = await file.arrayBuffer();
      const result = await this.calcArrayBuffer(buffer);
      onProgress?.(file.size, file.size, 100);
      return result;
    }

    // 对于大文件，使用分块流式计算
    return this.calcFileStreaming(file, chunkSize, onProgress);
  },

  /**
   * 流式计算文件MD5哈希值 - 分块处理大文件
   */
  async calcFileStreaming(
    file: File,
    chunkSize: number = 8 * 1024 * 1024,
    onProgress?: Md5ProgressHandler,
  ): Promise<string> {
    const hash = CryptoJS.algo.MD5.create();
    const totalSize = file.size;
    let processed = 0;

    console.log(
      `开始流式计算文件MD5: ${file.name}, 大小: ${(totalSize / (1024 * 1024)).toFixed(2)}MB`,
    );

    // 分块读取并计算
    while (processed < totalSize) {
      const start = processed;
      const end = Math.min(processed + chunkSize, totalSize);
      const currentChunkSize = end - start;

      try {
        // 读取当前块
        const chunk = file.slice(start, end);
        const arrayBuffer = await chunk.arrayBuffer();
        const uint8Array = new Uint8Array(arrayBuffer);

        // 将数据块添加到哈希计算中
        const wordArray = CryptoJS.lib.WordArray.create(uint8Array);
        hash.update(wordArray);

        processed += currentChunkSize;

        // 计算并报告进度
        const progress = (processed / totalSize) * 100;

        // 调用进度回调
        if (onProgress) {
          onProgress(processed, totalSize, progress);
        }

        // 打印进度（每处理10MB打印一次）
        if (processed % (10 * 1024 * 1024) === 0 || processed === totalSize) {
          console.log(
            `MD5计算进度: ${progress.toFixed(1)}% (${(processed / (1024 * 1024)).toFixed(2)}MB / ${(totalSize / (1024 * 1024)).toFixed(2)}MB)`,
          );
        }

        // 手动触发垃圾回收提示
        if (typeof window !== 'undefined' && (window as any).gc) {
          try {
            (window as any).gc();
          } catch {
            // 忽略 gc 调用失败
          }
        }
      } catch (error) {
        console.error(`MD5计算失败，块位置: ${start}-${end}`, error);
        throw new Error(
          `MD5计算失败: ${error instanceof Error ? error.message : '未知错误'}`,
        );
      }
    }

    const result = hash.finalize().toString(CryptoJS.enc.Hex).toLowerCase();
    console.log(`文件MD5计算完成: ${result}`);
    return result;
  },

  /**
   * 计算ArrayBuffer的MD5哈希值
   */
  async calcArrayBuffer(buffer: ArrayBuffer): Promise<string> {
    // 将ArrayBuffer转换为Uint8Array，然后创建WordArray
    const uint8Array = new Uint8Array(buffer);
    const wordArray = CryptoJS.lib.WordArray.create(uint8Array);
    return CryptoJS.MD5(wordArray).toString(CryptoJS.enc.Hex).toLowerCase();
  },

  /**
   * 计算字节数组的MD5哈希值
   */
  async calcBytes(bytes: Uint8Array): Promise<string> {
    const wordArray = CryptoJS.lib.WordArray.create(bytes);
    return CryptoJS.MD5(wordArray).toString(CryptoJS.enc.Hex).toLowerCase();
  },

  /**
   * 计算字符串的MD5哈希值
   */
  async calcString(str: string): Promise<string> {
    return CryptoJS.MD5(str).toString(CryptoJS.enc.Hex).toLowerCase();
  },
};

/**
 * 格式化文件大小
 */
export const StringFormat = {
  formatSize(bytes: number): string {
    if (bytes === 0) return '0 B';

    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
  },

  /**
   * 格式化上传速度
   */
  formatSpeed(bytesPerSecond: number): string {
    if (
      !bytesPerSecond ||
      bytesPerSecond <= 0 ||
      !Number.isFinite(bytesPerSecond)
    )
      return '0 B/s';
    const k = 1024;
    const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'];
    const i = Math.floor(Math.log(bytesPerSecond) / Math.log(k));
    const value = bytesPerSecond / k ** i;
    return `${value.toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`;
  },
};
